#include "particles.h"

#include "scene.h"
#include "textures.h"
#include <algorithm>
#include "gl/glut.h"
#include <iostream>

using namespace Scene;
using namespace Textures;

namespace Particles
{
	void particle_t::draw( const vector4 & invCameraPos, const matrix44 & invViewMatrix)
	{
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		matrix44 R = TranslateMatrix44(invCameraPos.x, invCameraPos.y, invCameraPos.z) * invViewMatrix;
		matrix44 matrix_particle = TranslateMatrix44(position.x, position.y, position.z) *
			R * RotateRadMatrix44(vector3(0, 0, 1),DegToRad(rotation)) * ScaleMatrix44(2*size, 2*size, 1);//
		glMultMatrixf(&(matrix_particle[0][0]));

		float color = age / 4.0f;
		glBegin(GL_QUADS);
			glNormal3f(0,0,-1);
			
			glMultiTexCoord2f(GL_TEXTURE0, 0,0);
			glMultiTexCoord2f(GL_TEXTURE1, color, 0.5f);
			glVertex3f(-0.5f, -0.5, 0.0f);
			
			glMultiTexCoord2f(GL_TEXTURE0, 1,0);
			glMultiTexCoord2f(GL_TEXTURE1, color, 0.5f);
			glVertex3f(0.5f, -0.5, 0.0f);
			
			glMultiTexCoord2f(GL_TEXTURE0, 1,1);
			glMultiTexCoord2f(GL_TEXTURE1, color, 0.5f);
			glVertex3f(0.5f, 0.5, 0.0f);
			
			glMultiTexCoord2f(GL_TEXTURE0, 0,1);
			glMultiTexCoord2f(GL_TEXTURE1, color, 0.5f);
			glVertex3f(-0.5f, 0.5, 0.0f);
		glEnd();

		glPopMatrix();
	}

	ParticleCameraDistComparer::ParticleCameraDistComparer(matrix44 & viewMatrix):
		cameraMatrix(viewMatrix)
	{

	}

	bool ParticleCameraDistComparer::operator() (particle_t i,particle_t j)
	{
		vector4 vi = cameraMatrix * vector4(i.position);
		vector4 vj = cameraMatrix * vector4(j.position);
		return vi.z < vj.z;
	}

	ParticleUpdater::ParticleUpdater(float psize,float pscale):size(psize), scale(pscale)
	{
	}

	void ParticleUpdater::setDt(float dtime)
	{
		dt = dtime;
	}

	particle_t ParticleUpdater::operator() (particle_t particle) {		
		particle.age += dt; 
		particle.position += particle.velocity * dt;
		particle.size += scale * size * dt; 
		particle.rotation += particle.rvelocity; 
		return particle;
	};

	ParticleDrawer::ParticleDrawer(vector4 camPos, matrix44 viewMatrix): invCamPos(-camPos), 
		invViewMatrix(InvertMatrix44(viewMatrix))
	{
	}

	void ParticleDrawer::operator() (particle_t p)
	{
		p.draw(invCamPos, invViewMatrix);
	}

	ParticleRemover::ParticleRemover(float timeToLive):ttl(timeToLive)
	{
	}

	void ParticleRemover::setTTL(float timeToLive)
	{
		ttl = timeToLive;
	}

	bool ParticleRemover::operator() (particle_t p) {		
		return p.age > ttl;
	};

	ParticleSystem::ParticleSystem(void)
	{
		init();
	}

	float ParticleSystem::rand_FloatRange(float a, float b)
	{
		return ((b-a)*((float)rand()/RAND_MAX))+a;
	}

	void ParticleSystem::init()
	{
		// Particle system's parameters copied from OGRE sample 
		time_to_live  = 4.0f; 
		// time of particles's life in seconds 
 
		emission_rate = 10.0f;   
		// number of particle's to be born per second 
 
		direction = vector3(.0f, 0.0f, 1.0f);   
		// mean direction of particle's velocity 
 
		angle = 11.0f;   
		// maximal angle declination from mean direction in degrees 
 
		velocity_min = 0.3f;   
		// minimal value of particle's velocity 
 
		velocity_max = 0.48f;   
		// maximal value of particle's velocity 
 
		size = 0.08f;       
		// initial size of particle 
 
		scale = 1.0f;       
		// size' = size+size*scale*time 
 
		rotation_speed_min = -2.0f;   
		// minimal rotation speed in angles per second 
 
		rotation_speed_max = 2.0f;   
		// maximal rotation speed in angles per second 
 
		max_particles = 500; 
		
		last_emision_time = 0.0f;

		emission_freq = 1.0f / emission_rate;

		source_position = vector3(-1.3f,-.14f,-0.56f);
		updater = ParticleUpdater(size, scale);
		remover = ParticleRemover(time_to_live);
	}

	void ParticleSystem::update_particles(float time)
	{
		last_emision_time += time;
		int toCreate = last_emision_time * emission_rate; // troche liczenia, zeby nie dodawac w jednej chwili duzo czasteczek, ale utrzymac ilosc emitowanych czasteczek
		if(toCreate > 0)									// zgodnie z emission_rate
			last_emision_time -= emission_freq * toCreate;
		for(int i=0; i < toCreate && particlesList.size() < max_particles; ++i) // dodawanie nowych czasteczek
		{
			particle_t p;
			p.age = 0.0f;
			p.size = size;
			p.position = source_position;
			p.velocity = vector3(rand_FloatRange(0.0f, 1.0f) * tan(DegToRad(angle)), rand_FloatRange(0.0f, 1.0f) * tan(DegToRad(angle)), 1.0f).normalize() * 
				rand_FloatRange(velocity_min, velocity_max);
			p.rvelocity = rand_FloatRange(rotation_speed_min, rotation_speed_max);
			p.rotation = 0.0f;
			particlesList.push_back(p);
		}
		updater.setDt(time);
		// uaktualniamy stan czasteczek
		std::transform(particlesList.begin(), particlesList.end(), particlesList.begin(), updater);
		// kasujemy stare czasteczki
		std::list<particle_t>::iterator first_removed = std::remove_if(particlesList.begin(), particlesList.end(), remover);
		particlesList.erase(first_removed, particlesList.end());
		// sortujemy czasteczki po zecie
		particlesList.sort(ParticleCameraDistComparer(Scene::matView));
	}

	void ParticleSystem::render_particles(const vector4 & cameraPosition, const matrix44 & viewMatrix)
	{
		glDepthMask(GL_FALSE);
		glEnable(GL_BLEND); 
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDisable(GL_LIGHTING);

		glActiveTexture(GL_TEXTURE0);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, tex_smoke);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		glActiveTexture(GL_TEXTURE1);
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, tex_smokecolors);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		// kolorowy dym, troche bardziej przezroczysty
		glColor4f(1,0,0,0.3);

		glActiveTexture(GL_TEXTURE0); // mieszanie koloru dymu z pierwsza tekstura
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_TEXTURE);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_PREVIOUS);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_TEXTURE);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_PREVIOUS);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);

		glActiveTexture(GL_TEXTURE1); // nalozenie drugiej tekstury
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);  
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);

		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
		glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
		// rysujemy wszystkie czasteczki
		std::for_each(particlesList.begin(), particlesList.end(), ParticleDrawer(cameraPosition, viewMatrix));

		glEnable(GL_LIGHTING);
		glDisable(GL_TEXTURE_2D);
		glActiveTexture(GL_TEXTURE0);
		glDisable(GL_TEXTURE_2D);
		glDepthMask(GL_TRUE);
		glDisable(GL_BLEND); 
	}

	void drawSquare(void)
	{

	}
}